#include "ccms/nr/processes_tab.h"
#include "arlib/raii/dir_handle.h"
#include "arlib/exception/invalid_argument_exception.h"
#include "arlib/exception/errno_exception.h"
#include "arlib/exception/logic_exception.h"
#include <cstdlib> // atol
#include <cstring> // memset
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <cerrno>

#define THROW_INVALIDARGUMENTEXCEPTION(reason) throw arlib::exception::InvalidArgumentException(__FILE__, __LINE__, reason)
#define THROW_ERRNOEXCEPTION(reason, errnum) throw arlib::exception::ErrnoException(__FILE__, __LINE__, reason, errnum)
#define THROW_LOGICEXCEPTION(reason) throw arlib::exception::LogicException(__FILE__, __LINE__, reason)

#define FILENAME_MAX_LEN  128

using namespace ccms::nr;

namespace ccms {
namespace nr {

ProcessesTab::ProcessesTab()
{
}

ProcessesTab::~ProcessesTab()
{
}

void ProcessesTab::update(void)
{
    long pid = -1;
    arlib::raii::DirHandle dh;
    struct dirent entry = { 0 };
    struct dirent* result = NULL;
    bool processWatched = false;
    int errnum = 0;
    ProcessesMap::iterator procIter;

    // clear the 'exists' flag for all processes
    for(procIter = processesMap.begin(); procIter != processesMap.end(); procIter++) {
        procIter->second.setExists(false);
    }

    // update the processes map
    dh.reset(opendir("/proc"));
    if(!dh.get()) {
        THROW_ERRNOEXCEPTION("Could not open /proc directory", errno);
    }

    do {
        errnum = readdir_r(dh.get(), &entry, &result);
        if(errnum > 0) {
            THROW_ERRNOEXCEPTION("Could not read from /proc directory", errnum);
        } else if(errnum == 0 && result == NULL) {
            break; // end of directory stream reached
        }

        pid = atol(entry.d_name);   // check the entry name
        if(pid == 0) {              // if it's not a PID...
            continue;
        }

        if(!watchedProcessesSet.empty()) {
            if(!isProcessWatched(pid)) {
                continue;
            }
        }

        procIter = processesMap.find(pid);
        if(procIter == processesMap.end()) {
            processesMap[pid] = Process(pid);
            procIter = processesMap.find(pid);
        }

        try {
            procIter->second.update();
        } catch(arlib::exception::ErrnoException& e) {
            if(e.getErrnum() == ENOENT) {
                // the contents of the a given /proc/<PID>/ directory may be
                // gone by now but an entry in /proc directory still exists,
                // until we close it. that's why we're here...
//                fprintf(stderr, "Could not update process with pid: %d, system says \"No such file or directory\"\n", (int) pid);
                continue;
            } else {
                throw;
            }
        }
        procIter->second.setExists(true);
    } while(1);

    // remove all inexistent processes from processesMap
    for(procIter = processesMap.begin(); procIter  != processesMap.end();) {
        ProcessesMap::iterator eraseElemIt = procIter++;
        if(eraseElemIt->second.getExists() == false) {
            processesMap.erase(eraseElemIt);
        }
    }
}

const ProcessesMap& ProcessesTab::getProcessesMap(void) const
{
    return processesMap;
}

void ProcessesTab::registerWatchedProcess(const pid_t pid)
{
    if(!pid) {
        THROW_INVALIDARGUMENTEXCEPTION("pid must not be 0");
    }

    watchedProcessesSet.insert(pid);
}

void ProcessesTab::unregisterWatchedProcess(const pid_t pid)
{
    bool callbackUnregistered = false;
    WatchedProcessesSet::const_iterator iter;

    if(!pid) {
        THROW_INVALIDARGUMENTEXCEPTION("pid must not be 0");
    }

    iter = watchedProcessesSet.find(pid);
    if(iter == watchedProcessesSet.end()) {
        THROW_LOGICEXCEPTION("Could not unregister watched pid");
    }

    watchedProcessesSet.erase(iter);
}

bool ProcessesTab::isProcessWatched(const pid_t pid) const
{
    WatchedProcessesSet::const_iterator iter;

    iter = watchedProcessesSet.find(pid);
    if(iter != watchedProcessesSet.end()) {
        return true;
    } else {
        return false;
    }
}

const Process& ProcessesTab::getProcess(const pid_t pid) const
{
    ProcessesMap::const_iterator iter;

    iter = processesMap.find(pid);
    if(iter != processesMap.end())
        return iter->second;
    else {
        throw ProcessDoesNotExistException(__FILE__, __LINE__);
    }
}

} // namespace nr
} // namespace ccms
